import { VAxios } from "@/utils/http/axios/VAxios";
import {
  CreateAxiosOptions,
  RequestOptions,
  Result,
} from "@/utils/http/axios/types";
import { deepMerge } from "@/utils/data/merge";
import { ContentTypeEnum, ResultCodeEnum } from "@/enums/HttpEnum";
import { AxiosTransform } from "@/utils/http/axios/axiosTransform";
import axios, { AxiosResponse } from "axios";
import { checkResponseStatus } from "@/utils/http/axios/checkResponseStatus";
import { isString, isUrl } from "@/utils/judgment";
import { useAuthStoreOutSetup } from "@/store/modules/auth";

/**
 * @description 处理数据
 */
const transform: AxiosTransform = {
  /**
   * 请求发送前, 整理用户自定义的options，生成完整的config.
   * @param config
   * @param options
   */
  beforeRequestHook: (config, options) => {
    const { joinPrefix, urlPrefix, apiUrl } = options;

    // Step1: 拼接生成完整的请求.

    // 判断config中的url是否已经是一个完成的url.
    // 多数情况下我们在config.url 中仅配置一部分路径名而非完整的Url.
    const isUrlStr = isUrl(config.url as string);

    if (!isUrlStr && joinPrefix) {
      config.url = `${urlPrefix}${config.url}`;
    }

    // 如果config.url提供的不是一个完整的url, 且提供了正确的apiUrl, 则做拼接.
    if (!isUrlStr && apiUrl && isString(apiUrl)) {
      // 拼接变量和字符串.
      config.url = `${apiUrl}${config.url}`;
    }
    return config;
  },

  /**
   * @description 请求拦截器 onFulfilled.
   * @param config axios config
   * @param options 用户自定义配置
   */
  requestInterceptors: (config, options) => {
    const userStore = useAuthStoreOutSetup();
    // 为请求头加token.
    const token = userStore.getToken;
    if (token && config.headers && options.requestOptions?.withToken === true) {
      config.headers.token = token;
    }
    return config;
  },

  /**
   * @description 请求拦截器 onReject.
   * @param error 错误信息.
   */
  requestInterceptorsCatch: (error: any) => {
    const $message = window.$message;
    $message?.error(error.message);
  },

  /**
   * @description 响应拦截器 onFulfilled.
   * @param res 返回的数据.
   */
  responseInterceptors: (res: AxiosResponse) => {
    return res;
  },

  /**
   * @description 相应拦截器 onReject.
   * @param error 错误信息.
   */
  responseInterceptorsCatch: (error: any) => {
    const $message = window.$message;
    const $dialog = window.$dialog;
    const { response, code, message } = error || {};

    const msg: string =
      response && response.data && response.data.msg ? response.data.msg : "";

    // error转换为string, 方便判断网络异常错误.
    const err: string = error.toString();

    try {
      if (code === "ECONNABORTED" && message.indexOf("timeout") !== -1) {
        $message?.error("有一个请求响应超时, 请刷新页面重试!");
        return;
      }
      if (err && err.includes("Network Error")) {
        $dialog?.info({
          title: "网络异常",
          content: "请检查您的网络连接是否正常",
          positiveText: "确定",
          closable: false,
          maskClosable: false,
        });
        return Promise.reject(error);
      }
    } catch (error) {
      throw new Error(error as any);
    }

    // 请求被取消.
    const isCancel = axios.isCancel(error);

    if (!isCancel) {
      checkResponseStatus(error.response && error.response.status, msg);
    } else {
      console.warn(error, "请求被取消");
    }
    return Promise.reject(response?.data);
  },

  /**
   * @description 处理请求获得的数据
   * @param res 请求从后端获取的数据
   * @param options 配置项
   */
  transformRequestData: (
    res: AxiosResponse<Result>,
    options: RequestOptions
  ) => {
    // 读取配置项.
    const {
      isReturnNativeResponse,
      isTransformResponse,
      isShowMessage,
      successMessageText,
      isShowSuccessMessage,
      errorMessageText,
      isShowErrorMessage,
      errorMessageMode,
    } = options;

    // 是否返回原生响应头, 对于一些特殊的请求，需要获取返回的所有属性.
    if (isReturnNativeResponse) {
      return res;
    }

    // 不进行任何处理时，直接返回后端的数据
    // 代码直接读取code, data, msg 等信息
    if (!isTransformResponse) {
      return res.data;
    }

    const { data } = res;

    const $dialog = window.$dialog;
    const $message = window.$message;

    if (!data) {
      $message?.error("请求出错, 请稍后重试!");
      throw new Error("请求出错, 请稍后重试!");
    }

    // 后端统一返还类返回的内容, 包括码, 数据体, 消息.
    const { code, resultData, msg } = data;

    // 请求成功(有数据返回, 且数据中有"状态码", 且"状态码"为成功)
    const hasSuccess =
      data && Reflect.has(data, "code") && code === ResultCodeEnum.SUCCESS;

    // 是否显示提示信息
    if (isShowMessage) {
      // 显示提示信息的优先度为: 先显示前端自定义的提示消息, 若没有, 则是后端的 msg, 若没有, 则为默认.
      // 若成功则弹窗, 失败有两种处理方案, 第一种是message提示, 第二种是弹窗.
      if (hasSuccess && (successMessageText || isShowSuccessMessage)) {
        $dialog?.success({
          type: "success",
          content: successMessageText || msg || "操作成功!",
        });
      } else if (!hasSuccess && (errorMessageText || isShowErrorMessage)) {
        $message?.error(errorMessageText || msg || "操作失败!");
      } else if (!hasSuccess && errorMessageMode === "modal") {
        // 对于一些比较重要的错误, 需要弹窗提示.
        $dialog?.info({
          title: "提示",
          content: msg,
          positiveText: "确定",
          onPositiveClick: () => {
            return;
          },
        });
      }
    }

    // 请求成功, 返回数据体
    if (hasSuccess) {
      return resultData;
    }

    // 出错时, msg为后端传来的错误提示.
    let errorMsg = msg;

    const router = useRouter();

    // 根据不同的code对其进行处理.
    switch (code) {
      case ResultCodeEnum.ERROR:
        $message?.error(errorMsg);
        break;
      case ResultCodeEnum.TIMEOUT:
        if (router.currentRoute.value?.name === "Login") return;
        // 登录超时, 跳转回登录页
        errorMsg = "登录超时, 请重新登录!";
        $dialog?.warning({
          title: "提示",
          content: "登录身份已失效, 请重新登录!",
          positiveText: "确定",
          closable: false, // 不可关闭.
          maskClosable: false, // 遮罩可关闭
          onPositiveClick: () => {
            // store.clear();
            window.location.href = "/login";
          },
        });
        break;
    }
    throw new Error(errorMsg);
  },
};

/**
 * 封装的Axios请求.
 * 使接口类型中定义的所有属性变成可选项.
 * @param options 自定义的请求参数.
 */
const createAxios = (options?: Partial<CreateAxiosOptions>) => {
  // 默认的请求配置。
  const defaultConfig = {
    headers: { "Content-Type": ContentTypeEnum.JSON }, // 默认的数据格式为JSON
    timeout: 3 * 1000, // 响应时长
    transform: transform, // 处理方式
    requestOptions: {
      // 配置项，下面的选项都可以再独立的接口请求中覆盖。
      // 是否返回原生的响应头，当需要获取响应头时，该属性设置为true.
      isReturnNativeResponse: false,
      // 需要对返回数据进行处理
      isTransformResponse: true,
      // 是否需要显示提示信息
      isShowMessage: true,
      // 消息提示类型(显示提示信息或弹窗提示).
      errorMessageMode: "none",
      // 是否携带token
      withToken: false,
      // 忽略重复请求
      ignoreCancelToken: true,
      // 默认添加prefix到url上.
      joinPrefix: true,
      // 接口前缀(一个项目可能有多个不同的调用接口, 所以可能会重写)
      urlPrefix: "/api",
    },
  };

  // 利用深度合并，得到自定义的请求配置。
  const customConfig = deepMerge(defaultConfig, options || {});
  return new VAxios(customConfig);
};

export const http = createAxios();
